/*
	Copyright (C) 2014 Salil Bhagurkar

	This file is part of illusion

	illusion is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	illusion is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with illusion.  If not, see <http://www.gnu.org/licenses/>.
*/

/*
 * Contains James Molloy's code, reformatted to suit illusion
 * http://www.jamesmolloy.co.uk/
 * http://code.google.com/p/jamesm-tutorials/
 */

#include <klib/lib.h>
#include <klib/memory.h>
#include <arch/console.h>
#include <arch/portio.h>
#include <arch/arch.h>
#include <arch/multiboot.h>
#include <arch/power.h>
#include <arch/asmdecl.h>
#include <arch/page.h>
#include <kernel/console.h>
#include <kernel/init.h>
#include <kernel/page.h>
#include <kernel/vmpage.h>
#include <kernel/syscall.h>
#include <kernel/lfault.h>
#include <kernel/drivers.h>
#include <kernel/irq.h>
#include <kernel/process.h>
#include <kernel/sched.h>
#include <kernel/errors.h>
#include <kernel/exception.h>

struct gdt_entry {
	unsigned short limit_low;           // The lower 16 bits of the limit.
	unsigned short base_low;            // The lower 16 bits of the base.
	unsigned char base_middle;         // The next 8 bits of the base.
	unsigned char access;              // Access flags, determine
					//what ring this segment can be used in.
	unsigned char granularity;
	unsigned char base_high;           // The last 8 bits of the base.
}__attribute__((packed));


struct gdt_ptr {
	unsigned short limit;	// The upper 16 bits of all selector limits.
	unsigned long base;	// The address of the first gdt_entry_t struct.
}__attribute((packed));

//TODO In each interrupt, we are loading the kernel segment registers. Required? Hint: TSS

struct tss_entry {
	unsigned long prev_tss;
	// The stack pointer to load when we change to kernel mode.
	unsigned long esp0;
	// The stack segment to load when we change to kernel mode.
	unsigned long ss0;
	// Unused...
	unsigned long esp1;
	unsigned long ss1;
	unsigned long esp2;
	unsigned long ss2;
	unsigned long cr3;
	unsigned long eip;
	unsigned long eflags;
	unsigned long eax;
	unsigned long ecx;
	unsigned long edx;
	unsigned long ebx;
	unsigned long esp;
	unsigned long ebp;
	unsigned long esi;
	unsigned long edi;
	//Values to be loaded when we switch to kernel mode
	unsigned long es;
	unsigned long cs;
	unsigned long ss;
	unsigned long ds;
	unsigned long fs;
	unsigned long gs;
	unsigned long ldt;
	unsigned long iomap_base;
}__attribute__((packed));

static struct tss_entry tss_entry;


#define NR_GDT 8

static struct gdt_entry gdt_entries[NR_GDT];
static struct gdt_ptr gdt_ptr;

static void gdt_set_gate(int num, unsigned long base, unsigned long limit,
		unsigned char access, unsigned char gran)
{
	gdt_entries[num].base_low    = (base & 0xFFFF);
	gdt_entries[num].base_middle = (base >> 16) & 0xFF;
	gdt_entries[num].base_high   = (base >> 24) & 0xFF;

	gdt_entries[num].limit_low   = (limit & 0xFFFF);
	gdt_entries[num].granularity = (limit >> 16) & 0x0F;

	gdt_entries[num].granularity |= gran & 0xF0;
	gdt_entries[num].access      = access;
}

static void write_tss(unsigned long num, unsigned long ss0, unsigned long esp0)
{
	unsigned long base = (unsigned long) &tss_entry;
	unsigned long limit = base + sizeof(tss_entry);

	gdt_set_gate(num, base, limit, 0xE9, 0x00);

	memset(&tss_entry, 0, sizeof(tss_entry));

	tss_entry.ss0  = ss0;  // Set the kernel stack segment.
	tss_entry.esp0 = esp0; // Set the kernel stack pointer.

	//Here we set the cs, ss, ds, es, fs and gs entries in the TSS.
	//These specify what segments should be loaded when the processor
	//switches to kernel mode. Therefore they are just our normal kernel
	//code/data segments - 0x08 and 0x10 respectively, but with the last
	//two bits set, making 0x0b and 0x13. The setting of these bits sets
	//the RPL (requested privilege level) to 3, meaning that this TSS can
	//be used to switch to kernel mode from ring 3.
	tss_entry.cs   = 0x0b;
	tss_entry.ss = tss_entry.ds = tss_entry.es = tss_entry.fs =
			tss_entry.gs = 0x13;

	tss_entry.iomap_base = 0xffff0000;
}

/*
 * This is called when we switch to or start a new process.
 * The processor finds the esp to be loaded after the
 * process switches to kernel mode.
 */
void set_current_kernel_stack(u32 esp)
{
	write_tss(7, 0x10, esp);

	_gdt_flush ((unsigned long)&gdt_ptr);
	_tss_flush();
}


static void init_gdt()
{
	gdt_ptr.limit = sizeof(struct gdt_entry) * NR_GDT - 1;
	gdt_ptr.base = (unsigned long)&gdt_entries;

	gdt_set_gate (0, 0, 0, 0, 0);             // Null segment.
	gdt_set_gate (1, 0, 0xFFFFF, 0x9A, 0xCF); // Code segment.
	gdt_set_gate (2, 0, 0xFFFFF, 0x92, 0xCF); // Data segment.
	gdt_set_gate (3, 0, 0xFFFFF, 0xFA, 0xCF); // User mode code segment.
	gdt_set_gate (4, 0, 0xFFFFF, 0xF2, 0xCF); // User mode data segment.
	gdt_set_gate (5, 0, 0xFFFFF, 0x9A, 0x0F); // Real mode code segment.
	gdt_set_gate (6, 0, 0xFFFFF, 0x92, 0x0F); // Real mode data segment.
	write_tss(7, 0x10, 0x90000);

	_gdt_flush ((unsigned long)&gdt_ptr);
	_tss_flush();
}


struct registers{
	// Data segment selector.
	unsigned long ds;
	// Pushed by pusha.
	unsigned long edi, esi, ebp, esp, ebx, edx, ecx, eax;
	// Interrupt number and error code (if applicable).
	unsigned long int_no, err_code;
	// Pushed by the processor automatically.
	unsigned long eip, cs, eflags, useresp, ss;
}__attribute__((packed));

struct registers_syscall {
	// Data segment selector.
	unsigned long ds;
	// Pushed by pusha.
	unsigned long edi, esi, ebp, esp, ebx, edx, ecx, eax;
	// Pushed by the processor automatically.
	unsigned long eip, cs, eflags, useresp, ss;
}__attribute__((packed));

static int isbit(unsigned long eflags, int bit)
{
	if(eflags & (1 << bit))
		return 1;
	return 0;
}

static void print_eflags(unsigned long eflags)
{
	console_printf("eflags: \n");
	console_printf("CF AF PF ZF SF TF IF DF OF IOPL NT RF VM AC VIF VIP ID\
\n");
	console_printf(" %u  %u  %u  %u  %u  %u  %u  %u  %u  %u%u   %u  %u  %u\
%u  %u   %u   %u\n", isbit(eflags, 0), isbit(eflags, 2),
			isbit(eflags, 4), isbit(eflags, 6), isbit(eflags, 7),
			isbit(eflags, 8), isbit(eflags, 9), isbit(eflags, 10),
			isbit(eflags, 11), isbit(eflags, 12),isbit(eflags, 13),
			isbit(eflags, 14), isbit(eflags, 16), isbit(eflags, 17),
			isbit(eflags, 18), isbit(eflags, 19), isbit(eflags, 20),
			isbit(eflags, 21));
}

void print_state(void *state)
{
	struct registers *regs = (struct registers *)state;
	console_printf("cs = 0x%x ds = 0x%x ss = 0x%x\n", regs->cs, regs->ds,
			regs->ss);
	console_printf("eax = 0x%x ebx = 0x%x ecx = 0x%x edx = 0x%x\n",
			regs->eax, regs->ebx, regs->ecx, regs->edx);
	console_printf("esi = 0x%x edi = 0x%x\n", regs->esi, regs->edi);
	console_printf("esp = 0x%x ebp = 0x%x\n", regs->esp, regs->ebp);
	console_printf("eip = 0x%x eflags = 0x%x\n", regs->eip, regs->eflags);
	console_printf("cr0 = 0x%x cr2 = 0x%x cr3 = 0x%x\n",
			_read_cr0(), _read_cr2(), _read_cr3());
	print_eflags(regs->eflags);
}

static void print_syscall_regs(struct registers_syscall *regs)
{
	console_printf("cs = 0x%x ds = 0x%x ss = 0x%x\n", regs->cs, regs->ds,
			regs->ss);
	console_printf("eax = 0x%x ebx = 0x%x ecx = 0x%x edx = 0x%x\n",
			regs->eax, regs->ebx, regs->ecx, regs->edx);
	console_printf("esi = 0x%x edi = 0x%x\n", regs->esi, regs->edi);
	console_printf("esp = 0x%x ebp = 0x%x\n", regs->esp, regs->ebp);
	console_printf("eip = 0x%x eflags = 0x%x\n", regs->eip, regs->eflags);
	console_printf("cr0 = 0x%x cr2 = 0x%x cr3 = 0x%x\n",
			_read_cr0(), _read_cr2(), _read_cr3());
	print_eflags(regs->eflags);
}

struct idt_entry {
	// The lower 16 bits of the address to jump to.
	unsigned short base_lo;
	unsigned short sel;            // Kernel segment selector.
	unsigned char always0;        // This must always be zero.
	unsigned char flags;          // More flags. See documentation.
	// The upper 16 bits of the address to jump to.
	unsigned short base_hi;
}__attribute__((packed));

struct idt_ptr {
	unsigned short limit;
	// The address of the first element in our idt_entry_t array.
	unsigned long base;
}__attribute__((packed));

struct idt_entry idt_entries[256];
struct idt_ptr idt_ptr;

char *exceptions[] = {"Divide by Zero", "Debug Exception",
	"Non-Maskable Interrupt", "Breakpoint Exception", "Overflow Exception",
	"Bound - Range Exception", "Invalid Opcode",
	"Device not available (x87)", "Double Fault",
	"Coprocessor segment overrun", "Invalid TSS", "Segment not present",
	"Stack Exception", "General Protection Fault", "Page Fault", "Reserved",
	"x87 FPU Exception", "Alignment Check Exception",
	"Machine Check Exception", "SIMD Floating Point Exception",
	/*20*/"Reserved", /*21*/"Reserved", /*22*/"Reserved", /*23*/"Reserved",
	/*24*/"Reserved", /*25*/"Reserved", /*26*/"Reserved", /*27*/"Reserved",
	/*28*/"Reserved", /*29*/"Reserved", "Security Exception", "Reserved"};


static int __handle_page_fault(struct registers *regs)
{
	void *fault_address = (void *)_read_cr2();
	if(fault_address == null) {
		console_printf("Null reference detected!\n");
		console_printf("An uninitialized pointer was used to \
access data\n");
		return -1;
	} else {
		unsigned long error_code = regs->err_code;
		unsigned long fault_flags = 0;
		debug("Page fault error code = 0x%x\n", error_code);
		if(error_code & PF_PRESENT) {
			fault_flags |= LF_VIOLATION;
		} else {
			fault_flags |= LF_ABSENT;
		}
		if(error_code & PF_US) {
			fault_flags |= LF_USER;
		} else {
			fault_flags |= LF_KERNEL;
		}
		if(error_code & PF_WR) {
			fault_flags |= LF_WRITE;
		} else {
			fault_flags |= LF_READ;
		}
		if(error_code & PF_RSVD) {
			fault_flags |= LF_UNEXPECTED;
		}
		if(error_code & PF_ID) {
			fault_flags |= LF_CODE;
		} else {
			fault_flags |= LF_DATA;
		}
		//Call the lfault page fault handler
		return handle_page_fault(fault_address, fault_flags);
	}
}

void idt_handler (struct registers *regs)
{
	bool fatal = false;
	if(regs->int_no < 32) {
		if(regs->int_no == 14) {
			//Return if the page fault was successfully handled
			if(!__handle_page_fault(regs))
				return;
			if(!((regs->err_code) & PF_US))
				fatal = true;
			console_printf("Faulting address in %s space\n",
				((regs->err_code) & PF_US) ? "user" : "kernel");
		}
		handle_exception(regs->err_code, exceptions[regs->int_no], regs, fatal);
	} else {
		console_printf("Unhandled interrupt (%u)\n", regs->int_no);
	}
}

//Called by _isr128
int syscall_handler(struct registers_syscall *regs)
{
	int err = syscall(regs->eax, regs->ebx, regs->ecx, regs->edx, regs->esi,
			regs->edi);
	return err;
}

static void __default_timer_handler()
{
}

/*
 * This is saved in order to restore it on bios calls
 */
static unsigned long pm_freq = 18;

/*
 * Code by Brendan on forum.osdev.org
 * http://forum.osdev.org/viewtopic.php?f=1&t=10841
 */
static void timer_phase(int hz)
{
	int divisor;
	int intr = 0;

	divisor = 1193180 / hz;              /* Calculate our divisor */
	if(divisor == 0) {
		debug("WARNING: PIT frequency too high, using 1193180 Hz.\n");
		divisor = 1;
	}
	if(divisor == 1) {
		intr = interrupts_enabled();
		if(intr)
			disable_interrupts();
		outb(0x43, 0x34);             /* Set our command byte 0x34 (mode 2) */
		outb(0x40, divisor & 0xFF);   /* Set low byte of divisor */
		outb(0x40, divisor >> 8);     /* Set high byte of divisor */
		if(intr)
			enable_interrupts();
	} else {
		if(divisor >= 65536) {
			if(divisor > 65536) {
				debug("WARNING: PIT frequency too low, using 18.2 Hz.\n");
			}
			divisor = 0;
		}
		intr = interrupts_enabled();
		if(intr)
			disable_interrupts();
		outb(0x43, 0x36);             /* Set our command byte 0x36 (mode 3) */
		outb(0x40, divisor & 0xFF);   /* Set low byte of divisor */
		outb(0x40, divisor >> 8);     /* Set high byte of divisor */
		if(intr)
			enable_interrupts();
	}
}

static void (*timer_handler)() = __default_timer_handler;

void set_timer_handler(void (*handler)(), int frequency)
{
	//Save the frequency through bios calls
	pm_freq = frequency;
	timer_phase(frequency);
	timer_handler = handler;
}

void reset_timer_handler()
{
	timer_handler = __default_timer_handler;
}


// This gets called from our ASM interrupt handler stub.
void irq_handler(struct registers *regs)
{
	if(regs->int_no == 0)
		timer_handler();
	else
		handle_irq(regs->int_no);	//Call the kernel IRQ handler
	if (regs->int_no >= 8) {
		outb(0xA0, 0x20);		// Send reset signal to slave.
	}
	outb(0x20, 0x20);			// Send reset signal to master. (As well as slave, if necessary).
}

static void idt_set_gate(unsigned char num, unsigned long base,
		unsigned int sel)
{
	idt_entries[num].base_lo = base & 0xFFFF;
	idt_entries[num].base_hi = (base >> 16) & 0xFFFF;

	idt_entries[num].sel     = sel;
	idt_entries[num].always0 = 0;

	//ring 0
	//idt_entries[num].flags   = 0x8E;

	//ring 3
	idt_entries[num].flags = 0xEE;
}

static void map_irqs(unsigned char irq_start0, unsigned char irq_start1)
{
	// Remap the irq table.
	outb(0x20, 0x11);
	outb(0xA0, 0x11);
	outb(0x21, irq_start0);
	outb(0xA1, irq_start1);
	outb(0x21, 0x04);
	outb(0xA1, 0x02);
	outb(0x21, 0x01);
	outb(0xA1, 0x01);
	outb(0x21, 0x0);
	outb(0xA1, 0x0);
}

static void init_idt()
{
	idt_ptr.limit = sizeof(struct idt_entry) * 256 - 1;
	idt_ptr.base  = (unsigned long)&idt_entries;

	memset(&idt_entries, 0, sizeof(struct idt_entry) * 255);

	void *fns[] = {_isr0, _isr1, _isr2, _isr3, _isr4, _isr5, _isr6, _isr7,
			_isr8, _isr9, _isr10, _isr11, _isr12, _isr13, _isr14,
			_isr15, _isr16, _isr17, _isr18, _isr19, _isr20, _isr21,
			_isr22, _isr23, _isr24, _isr25, _isr26, _isr27, _isr28,
			_isr29, _isr30, _isr31, _isr32, _isr33, _isr34, _isr35,
			_isr36, _isr37, _isr38, _isr39, _isr40, _isr41, _isr42,
			_isr43, _isr44, _isr45, _isr46, _isr47, _isr48, _isr49,
			_isr50, _isr51, _isr52, _isr53, _isr54, _isr55, _isr56,
			_isr57, _isr58, _isr59, _isr60, _isr61, _isr62, _isr63,
			_isr64, _isr65, _isr66, _isr67, _isr68, _isr69, _isr70,
			_isr71, _isr72, _isr73, _isr74, _isr75, _isr76, _isr77,
			_isr78, _isr79, _isr80, _isr81, _isr82, _isr83, _isr84,
			_isr85, _isr86, _isr87, _isr88, _isr89, _isr90, _isr91,
			_isr92, _isr93, _isr94, _isr95, _isr96, _isr97, _isr98,
			_isr99, _isr100, _isr101, _isr102, _isr103, _isr104,
			_isr105, _isr106, _isr107, _isr108, _isr109, _isr110,
			_isr111, _isr112, _isr113, _isr114, _isr115, _isr116,
			_isr117, _isr118, _isr119, _isr120, _isr121, _isr122,
			_isr123, _isr124, _isr125, _isr126, _isr127, _isr128,
			_isr129, _isr130, _isr131, _isr132, _isr133, _isr134,
			_isr135, _isr136, _isr137, _isr138, _isr139, _isr140,
			_isr141, _isr142, _isr143, _isr144, _isr145, _isr146,
			_isr147, _isr148, _isr149, _isr150, _isr151, _isr152,
			_isr153, _isr154, _isr155, _isr156, _isr157, _isr158,
			_isr159, _isr160, _isr161, _isr162, _isr163, _isr164,
			_isr165, _isr166, _isr167, _isr168, _isr169, _isr170,
			_isr171, _isr172, _isr173, _isr174, _isr175, _isr176,
			_isr177, _isr178, _isr179, _isr180, _isr181, _isr182,
			_isr183, _isr184, _isr185, _isr186, _isr187, _isr188,
			_isr189, _isr190, _isr191, _isr192, _isr193, _isr194,
			_isr195, _isr196, _isr197, _isr198, _isr199, _isr200,
			_isr201, _isr202, _isr203, _isr204, _isr205, _isr206,
			_isr207, _isr208, _isr209, _isr210, _isr211, _isr212,
			_isr213, _isr214, _isr215, _isr216, _isr217, _isr218,
			_isr219, _isr220, _isr221, _isr222, _isr223, _isr224,
			_isr225, _isr226, _isr227, _isr228, _isr229, _isr230,
			_isr231, _isr232, _isr233, _isr234, _isr235, _isr236,
			_isr237, _isr238, _isr239, _isr240, _isr241, _isr242,
			_isr243, _isr244, _isr245, _isr246, _isr247, _isr248,
			_isr249, _isr250, _isr251, _isr252, _isr253, _isr254,
			_isr255};

	void *fn_irqs[] = {_irq0, _irq1, _irq2, _irq3, _irq4, _irq5, _irq6,
			_irq7, _irq8, _irq9, _irq10, _irq11, _irq12, _irq13,
			_irq14, _irq15};

	int i;
	for(i = 0; i < 256; i++) {
		idt_set_gate(i, (unsigned long)fns[i], 0x08);
	}

	for(i = 0; i < 16; i++) {
		idt_set_gate(i + 32, (unsigned long)fn_irqs[i], 0x08);
	}

	_idt_flush((unsigned long)&idt_ptr);

	map_irqs(0x20, 0x28);
}

static char intr_enabled = 0;

int interrupts_enabled()
{
	return intr_enabled;
}

void disable_interrupts()
{
	intr_enabled = 0;
	__asm__ volatile ("cli");
}

void enable_interrupts()
{
	intr_enabled = 1;
	__asm__ volatile ("sti");
}



static int serial_write(char *stream, unsigned long length)
{
	while(length-- > 0) {
		outb(0x3f8, (*stream));
		stream++;
	}
	return 0;
}

static int init_serial_console()
{
	return 0;
}

static struct console_driver serial_driver = {
	.flags = 0,
	.buffer = null,
	.update_buffer = device_dummy,
	.update_hardware_cursor = device_dummy,
	.update_size = device_dummy,
	.init = init_serial_console,
	.write = serial_write,
	.cursor_x = 0,
	.cursor_y = 0,
	.height = 0,
	.width = 0,
	.active = 1,
	.next = null,
	.prev = null};

/*Crucial inits*/
int setup_processor()
{
	init_gdt();
	init_idt();
	_wp_enable();
	register_console_driver(&x86_boot_console_driver);
	register_console_driver(&serial_driver);
	enable_interrupts();
	return 0;
}

int reserve_module_memory()
{
	return multiboot_reserve_module_memory();
}

int reserve_memory()
{
	//Reserve kernel physical & virtual memory for later identity mapping
	unsigned long size = _kernel_end - 0;
	unsigned long nr_pages = size / PAGE_SIZE + !!(size % PAGE_SIZE);
	initmsg("Reserve kernel memory [0x0 - 0x%x] ( %u pages )\n", _kernel_end,
			nr_pages);
	phys_reserve_pages(null, nr_pages);
	vm_reserve_pages(&kernel_vmmap, null, nr_pages);
	return 0;
}

int register_modules()
{
	return multiboot_register_modules();
}

/*Asm callable function for debugging */
void _print_var(unsigned long shit)
{
	console_printf("Value = 0x%x\n", shit);
}

static void wait_kbc()
{
	while((inb(0x64) & 2) != 0);
}

void reboot_system()
{
	wait_kbc();
	outb(0x64, 0xfe);
	halt_system();
}

/*************************************16 bit BIOS calls***************/



#include "irmm.h"

#define IRMM_DEST_ADDR 0x7F00

static char was_page_translating = 0, was_interrupting = 0;

static void restore_sys()
{
	//Get our good old GDT and IDT back
	_gdt_flush ((unsigned long)&gdt_ptr);
	_idt_flush((unsigned long)&idt_ptr);
	map_irqs(0x20, 0x28);
	timer_phase(pm_freq);
	if(was_interrupting)
		enable_interrupts();
}

static void prepare_bioscall()
{
	//We don't want to be interrupted before we finish
	was_interrupting = interrupts_enabled();
	if(was_interrupting) {
		disable_interrupts();
	}
	//Map the IRQs to the real mode setting
	map_irqs(0x8, 0x70);
	timer_phase(18);
}


#define RM_REG_MEM 0x7E00

extern void _callbios_pt();
extern void _callbios_no_pt();

void bios_call(struct bioscall_regs *regs)
{
	//Copy over the registers to the transfer location
	memcpy((void *)RM_REG_MEM, regs, sizeof(struct bioscall_regs));
	prepare_bioscall();

	//This will invoke the IRMM
	if(is_page_translating())
		_callbios_pt();
	else
		_callbios_no_pt();

	restore_sys();
	//Copy back the result registers
	memcpy(regs,(void *)RM_REG_MEM,sizeof(struct bioscall_regs));
}



/*Intel Real Mode Management code*/
static int irmm_init()
{
	initmsg("Transferring IRMM\n");
	memcpy((void *)IRMM_DEST_ADDR, bin, BIN_LENGTH); //transfer irmm

	//Make a test bioscall
	struct bioscall_regs regs;
	memset(&regs, 0, sizeof(struct bioscall_regs));
	regs.f = 0x11;
	bios_call(&regs);

	return 0;
}

int arch_init2()
{
	return irmm_init();
}
